using System.Text;

namespace Fadd
{
	/// <summary>
	/// Wrapper for the <see cref="StringBuilder"/> class adding support for tabbing easily
	/// </summary>
	/// <usage><![CDATA[
	/// TabStringBuilder sb = new TabStringBuilder();
	/// sb.AppendLineInc("<div>");
	/// sb.AppendLine(@"<input type=""button"" value=""My Button"" />);
	/// sb.AppendLineDec("</div>");
	/// 
	/// Would give the result:
	/// <div>
	///		<input type="button" value="My Button" />
	/// </div>
	/// 
	/// Without having you worry about what tab indentation your on, neat!
	/// ]]></usage>
	public class TabStringBuilder
	{
		private readonly StringBuilder _sb;

		private string _tabs = string.Empty;
		private int _tabIntendation;
	    private bool _tabsAdded;

		#region Constructors

		/// <summary>
		/// Instances a blank <see cref="TabStringBuilder"/>
		/// </summary>
		public TabStringBuilder() : this(new StringBuilder(), 0)
		{}

		/// <summary>
		/// Instance the <see cref="TabStringBuilder"/> with a <see cref="StringBuilder"/> and a start intendation
		/// </summary>
		/// <param name="stringBuilder">A <see cref="StringBuilder"/> to append text to</param>
		/// <param name="intendation">
		/// Can be used to start writing with tabs using a set intendation when data has previously been written to the
		/// <see cref="StringBuilder"/> with intendation.
		/// </param>
		public TabStringBuilder(StringBuilder stringBuilder, int intendation)
		{
			Check.Require(stringBuilder, "stringBuilder");
			_sb = stringBuilder;
			SetIntendation(intendation);
		}

		#endregion

		#region ToString method using the StringBuilders string

		/// <summary>
		/// The built string
		/// </summary>
		/// <returns>Generated string</returns>
		public override string ToString()
		{
			return _sb.ToString();
		}

		#endregion

		/// <summary>
		/// Changes the <see cref="_tabs"/> to contain the specified number of tab characters
		/// </summary>
		/// <param name="intendation">Number of tab characters to set</param>
		private void SetIntendation(int intendation)
		{
			intendation = intendation < 0 ? 0 : intendation;
            _tabs = string.Empty.PadLeft(intendation, '\t');
			_tabIntendation = intendation;
		}

		#region Append methods

		#region AppendLine methods

		/// <summary>
		/// Appends tabs followed by the specified value and a line termination to the string
		/// </summary>
		/// <param name="value">The value to add</param>
		public void AppendLine(string value)
		{
			Check.Require(value, "value");
            CheckTabs();
			SbAppendLine(value);
		}

		/// <summary>
		/// Appends tabs followed by the specified value and a line termination to the string
		/// </summary>
		/// <param name="value">The value to add</param>
		public void AppendLine(object value)
		{
			Check.Require(value, "value");
		    CheckTabs();
			SbAppendLine(value);
		}

		/// <summary>
		/// Appends a empty line without tabs
		/// </summary>
		public void AppendLine()
		{
            SbAppendLine();
		}

		#endregion

		#region Append methods

		/// <summary>
		/// Just appends plain string to the underlying <see cref="StringBuilder"/> without any tabs
		/// </summary>
		/// <param name="value">The value to add</param>
		public void Append(string value)
		{
			Check.Require(value, "value");
            CheckTabs();
            _sb.Append(value);
		}

		/// <summary>
		/// Appends tabs and value to the string
		/// </summary>
		/// <param name="value">The value to add</param>
		public void Append(object value)
		{
			Check.Require(value, "value");
            CheckTabs();
            _sb.Append(value);
		}

		#endregion

		#region AppendLineInc methods

		/// <summary>
		/// Adds a new line and increases the tab indendation
		/// </summary>
		/// <example><![CDATA[
		/// sb.Append("<div>");
		/// sb.AppendLineInc();
		/// sb.AppendLine("<img src.../>");
		/// sb.AppendLineDec("</div>");
		/// 
		/// =>
		/// <div>
		///		<img src.../>
		/// </div>
		/// ]]></example>
		public void AppendLineInc()
		{
            SbAppendLine();
			SetIntendation(_tabIntendation + 1);
		}

		/// <summary>
		/// Appends tabs and value to the string after which the intendation is increased
		/// </summary>
		/// <param name="value">The value to append</param>
		/// <remarks>The tab intendation is increased after adding the value</remarks>
		/// <example><![CDATA[
		/// sb.AppendLineInc("<div>");
		/// sb.AppendLine("<span>Example</span>");
		/// 
		/// Would give:
		/// <div>
		///		<span>Example</span>
		/// ]]></example>
		public void AppendLineInc(string value)
		{
			Check.Require(value, "value");
            CheckTabs();
            SbAppendLine(value);
			SetIntendation(_tabIntendation + 1);
		}

		/// <summary>
		/// Appends tabs and value to the string after which the intendation is increased
		/// </summary>
		/// <param name="value">The value to append</param>
		/// <remarks>The tab intendation is increased after adding the value</remarks>
		/// <example><![CDATA[
		/// sb.AppendLineInc("<div>");
		/// sb.AppendLine("<span>Example</span>");
		/// 
		/// Would give:
		/// <div>
		///		<span>Example</span>
		/// ]]></example>
		public void AppendLineInc(object value)
		{
			Check.Require(value, "value");
            CheckTabs();
            SbAppendLine(value);
			SetIntendation(_tabIntendation + 1);
		}

		#endregion

		#region AppendLineDec methods

		/// <summary>
		/// Decreases the intendation and appends tabs and value to the string
		/// </summary>
		/// <param name="value">The value to append</param>
		/// <remarks>The tab intendation is decreased before adding the value</remarks>
		/// <example><![CDATA[
		/// sb.AppendLineInc("<div>");
		/// sb.AppendLine("<span>Example</span>");
		/// sb.AppendLineDec("<div>");
		/// 
		/// Would give:
		/// <div>
		///		<span>Example</span>
		/// </div>
		/// ]]></example>
		public void AppendLineDec(string value)
		{
			Check.Require(value, "value");
			SetIntendation(_tabIntendation - 1);
            CheckTabs();
            SbAppendLine(value);
		}

		/// <summary>
		/// Decreases the intendation and appends tabs and value to the string
		/// </summary>
		/// <param name="value">The value to append</param>
		/// <remarks>The tab intendation is decreased before adding the value</remarks>
		/// <example><![CDATA[
		/// sb.AppendLineInc("<div>");
		/// sb.AppendLine("<span>Example</span>");
		/// sb.AppendLineDec("<div>");
		/// 
		/// Would give:
		/// <div>
		///		<span>Example</span>
		/// </div>
		/// ]]></example>
		public void AppendLineDec(object value)
		{
			Check.Require(value, "value");
			SetIntendation(_tabIntendation - 1);
            CheckTabs();
            SbAppendLine(value);
		}

		#endregion

		#endregion

        private void CheckTabs()
        {
            if (_tabsAdded) return;
            _tabsAdded = true;
            _sb.Append(_tabs);
        }

        private void SbAppendLine(object value)
        {
            _tabsAdded = false;
            _sb.AppendLine(value.ToString());
        }

        private void SbAppendLine()
        {
            _tabsAdded = false;
            _sb.AppendLine();
        }

		/// <summary>
		/// Retrieves the <see cref="StringBuilder"/> used by the class
		/// </summary>
		public StringBuilder StringBuilder
		{
			get{ return _sb;}
		}
	}
}
